// GalaxyGenerator.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using System.Xml.Schema;

using log4net;

using Supremacy.Collections;
using Supremacy.Data;
using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.Resources;
using Supremacy.Types;
using Supremacy.Utility;

using Wintellect.PowerCollections;
using System.Threading.Tasks;

namespace Supremacy.Universe
{
    public static class GalaxyGenerator
    {
        public const double MinDistanceBetweenStars = 1.25;
        public const int MinHomeworldDistanceFromInterference = 2;

        private const int MinorRaceLimit = 50;

        private static readonly ILog Log;

        private static TableMap UniverseTables;
        private static XmlSchemaSet XmlSchemas;

        private static readonly Dictionary<StarType, int> StarTypeDist;
        private static readonly DoubleKeyedSet<StarType, PlanetSize, int> StarTypeModToPlanetSizeDist;
        private static readonly DoubleKeyedSet<int, PlanetSize, int> SlotModToPlanetSizeDist;
        private static readonly DoubleKeyedSet<StarType, PlanetType, int> StarTypeModToPlanetTypeDist;
        private static readonly DoubleKeyedSet<PlanetSize, PlanetType, int> PlanetSizeModToPlanetTypeDist;
        private static readonly DoubleKeyedSet<int, PlanetType, int> SlotModToPlanetTypeDist;
        private static readonly DoubleKeyedSet<PlanetSize, MoonSize, int> PlanetSizeModToMoonSizeDist;
        private static readonly DoubleKeyedSet<PlanetType, MoonSize, int> PlanetTypeModToMoonSizeDist;

        static GalaxyGenerator()
        {
            UniverseTables = UniverseManager.Tables;
            Log = GameLogManager.GetLogger(typeof(GalaxyGenerator));

            StarTypeDist = new Dictionary<StarType, int>();
            StarTypeModToPlanetSizeDist = new DoubleKeyedSet<StarType, PlanetSize, int>();
            StarTypeModToPlanetTypeDist = new DoubleKeyedSet<StarType, PlanetType, int>();
            SlotModToPlanetSizeDist = new DoubleKeyedSet<int, PlanetSize, int>();
            SlotModToPlanetTypeDist = new DoubleKeyedSet<int, PlanetType, int>();
            PlanetSizeModToPlanetTypeDist = new DoubleKeyedSet<PlanetSize, PlanetType, int>();
            PlanetSizeModToMoonSizeDist = new DoubleKeyedSet<PlanetSize, MoonSize, int>();
            PlanetTypeModToMoonSizeDist = new DoubleKeyedSet<PlanetType, MoonSize, int>();

            foreach (var starType in EnumHelper.GetValues<StarType>())
            {
                StarTypeDist[starType] = Number.ParseInt32(UniverseTables["StarTypeDist"][starType.ToString()][0]);
                foreach (var planetSize in EnumHelper.GetValues<PlanetSize>())
                {
                    StarTypeModToPlanetSizeDist[starType, planetSize] =
                        Number.ParseInt32(
                            UniverseTables["StarTypeModToPlanetSizeDist"][starType.ToString()][planetSize.ToString()]);
                }
                foreach (var planetType in EnumHelper.GetValues<PlanetType>())
                {
                    StarTypeModToPlanetTypeDist[starType, planetType] =
                        Number.ParseInt32(
                            UniverseTables["StarTypeModToPlanetTypeDist"][starType.ToString()][planetType.ToString()]);
                }
            }

            for (var i = 0; i < StarSystem.MaxPlanetsPerSystem; i++)
            {
                foreach (var planetSize in EnumHelper.GetValues<PlanetSize>())
                {
                    SlotModToPlanetSizeDist[i, planetSize] =
                        Number.ParseInt32(UniverseTables["SlotModToPlanetSizeDist"][i][planetSize.ToString()]);
                }
                foreach (var planetType in EnumHelper.GetValues<PlanetType>())
                {
                    SlotModToPlanetTypeDist[i, planetType] =
                        Number.ParseInt32(UniverseTables["SlotModToPlanetTypeDist"][i][planetType.ToString()]);
                }
            }

            foreach (var planetSize in EnumHelper.GetValues<PlanetSize>())
            {
                foreach (var planetType in EnumHelper.GetValues<PlanetType>())
                {
                    PlanetSizeModToPlanetTypeDist[planetSize, planetType] =
                        Number.ParseInt32(
                            UniverseTables["PlanetSizeModToPlanetTypeDist"][planetSize.ToString()][planetType.ToString()
                                ]);
                }
            }

            foreach (var moonSize in EnumHelper.GetValues<MoonSize>())
            {
                foreach (var planetSize in EnumHelper.GetValues<PlanetSize>())
                {
                    PlanetSizeModToMoonSizeDist[planetSize, moonSize] =
                        Number.ParseInt32(
                            UniverseTables["PlanetSizeModToMoonSizeDist"][planetSize.ToString()][moonSize.ToString()]);
                }
                foreach (var planetType in EnumHelper.GetValues<PlanetType>())
                {
                    PlanetTypeModToMoonSizeDist[planetType, moonSize] =
                        Number.ParseInt32(
                            UniverseTables["PlanetTypeModToMoonSizeDist"][planetType.ToString()][moonSize.ToString()]);
                }
            }
        }

        private static void LoadSchemas()
        {
            XmlSchemas = new XmlSchemaSet();
            XmlSchemas.Add(
                "Supremacy:Supremacy.xsd",
                "Resources/Data/Supremacy.xsd");
            XmlSchemas.Add(
                "Supremacy:Races.xsd",
                "Resources/Data/Races.xsd");
            XmlSchemas.Add(
                "Supremacy:Civilizations.xsd",
                "Resources/Data/Civilizations.xsd");
        }

        private static void ValidateXml(object sender, ValidationEventArgs e)
        {
            Debugger.Break();
        }

        private static Collections.CollectionBase<string> GetStarNames()
        {
            var file = new FileStream(
                ResourceManager.GetResourcePath("Resources/Tables/StarNames.txt"),
                FileMode.Open,
                FileAccess.Read);

            var reader = new StreamReader(file);
            var names = new Collections.CollectionBase<string>();

            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();
                if (line == null)
                    break;

                names.Add(line.Trim());
            }

            return names;
        }

        private static IList<string> GetNebulaNames()
        {
            var file = new FileStream(
                ResourceManager.GetResourcePath("Resources/Tables/NebulaNames.txt"),
                FileMode.Open,
                FileAccess.Read);
            var reader = new StreamReader(file);
            var names = new List<string>();
            while (!reader.EndOfStream)
            {
                names.Add(reader.ReadLine().Trim());
            }
            return names;
        }

        private static int GetMinDistanceBetweenHomeworlds()
        {
            var size = Math.Min(
                GameContext.Current.Universe.Map.Width,
                GameContext.Current.Universe.Map.Height);

            // Ensure empireCount has a positive value to avoid a divide-by-zero error.
            var empireCount = Math.Max(1, GameContext.Current.Civilizations.Count(o => o.IsEmpire));

            return (size / empireCount);
        }

        public static void GenerateGalaxy(GameContext game)
        {
            GameContext.Push(game);
            try
            {
                while (true)
                {
                    /* We reload the Universe Tables so that any changes made to the tables
                     * during runtime will be applied without restarting the game.  This
                     * will be useful for tweaking the tables during development.  We can
                     * fall back to using UniverseManager.Tables later on.
                     */
                    UniverseTables = TableMap.ReadFromFile(
                        ResourceManager.GetResourcePath("Resources/Tables/UniverseTables.txt"));

                    var galaxySizes = UniverseTables["GalaxySizes"];

                    var mapSize = new Dimension(
                        Number.ParseInt32(galaxySizes[game.Options.GalaxySize.ToStringCached()]["Width"]),
                        Number.ParseInt32(galaxySizes[game.Options.GalaxySize.ToStringCached()]["Height"]));

                    var universe = new UniverseManager(mapSize);

                    GameContext.Current.Universe = universe;

                    LoadSchemas();

                    var starPositions = GetStarPositions();
                    var starNames = GetStarNames();

                    Algorithms.RandomShuffleInPlace(starNames);

                    Collections.CollectionBase<MapLocation> homeLocations;

                    if (!PlaceHomeworlds(starPositions, starNames, out homeLocations))
                        continue;

                    GenerateSystems(starPositions, starNames, homeLocations);
                    PlaceMoons();

                    break;
                }
            }
            finally
            {
                GameContext.Pop();
            }
        }

        private static Collections.CollectionBase<MapLocation> GetStarPositions()
        {
            IGalaxyLayout layout;

            var width = GameContext.Current.Universe.Map.Width;
            var height = GameContext.Current.Universe.Map.Height;
            var number = (width * height);

            switch (GameContext.Current.Options.StarDensity)
            {
                case StarDensity.Sparse:
                    number /= 12;
                    break;
                case StarDensity.Medium:
                    number /= 10;
                    break;
                case StarDensity.Dense:
                default:
                    number /= 8;
                    break;
            }

            switch (GameContext.Current.Options.GalaxyShape)
            {
                case GalaxyShape.Ring:
                    layout = new RingGalaxyLayout();
                    break;
                case GalaxyShape.Cluster:
                    layout = new ClusterGalaxyLayout();
                    break;
                case GalaxyShape.Spiral:
                    layout = new SpiralGalaxyLayout();
                    break;
                case GalaxyShape.Elliptical:
                    layout = new EllipticalGalaxyLayout();
                    break;
                default:
                case GalaxyShape.Irregular:
                    layout = new IrregularGalaxyLayout();
                    break;
            }

            ICollection<MapLocation> positions;

            var positionsGenerated = layout.GetStarPositions(out positions, number, width, height);
            var result = new Collections.CollectionBase<MapLocation>(positions.Count);
            
            positions.CopyTo(result);

            return result;
        }

        private static StarSystemDescriptor GenerateHomeSystem(Civilization civ)
        {
            var system = new StarSystemDescriptor();
            StarType starType;

            while (!(starType = GetStarType()).SupportsPlanets() || (starType == StarType.Nebula))
                continue;

            system.StarType = starType;
            system.Name = civ.HomeSystemName;
            system.Inhabitants = civ.Race.Key;
            system.Bonuses = (civ.CivilizationType == CivilizationType.MinorPower)
                                 ? SystemBonus.RawMaterials
                                 : SystemBonus.Dilithium | SystemBonus.RawMaterials;

            GeneratePlanetsWithHomeworld(system, civ);

            return system;
        }

        private static void SetPlanetNames(StarSystem system)
        {
            if (system == null)
                throw new ArgumentNullException("system");
            for (var i = 0; i < system.Planets.Length; i++)
            {
                if (String.IsNullOrEmpty(system.Planets[i].Name))
                {
                    system.Planets[i].Name = (system.Planets[i].PlanetType == PlanetType.Asteroids)
                                                 ? "Asteroids"
                                                 : system.Name + " " + RomanNumber.Get(i + 1);
                }
            }
        }

        private static int GetIdealSlot(StarSystemDescriptor system, PlanetDescriptor planet)
        {
            var bestScore = 0;
            var bestSlot = 0;
            for (var iSlot = 0; iSlot <= system.Planets.Count; iSlot++)
            {
                var score =
                    GetPlanetSizeScore(
                        system.StarType.Value,
                        planet.Size.Value,
                        iSlot)
                    + GetPlanetTypeScore(
                          system.StarType.Value,
                          planet.Size.Value,
                          planet.Type.Value,
                          iSlot);
                if (score > bestScore)
                {
                    bestScore = score;
                    bestSlot = iSlot;
                }
            }
            return bestSlot;
        }

        private static bool PlaceHomeworlds(Collections.CollectionBase<MapLocation> positions,
            IList<string> starNames,
            out Collections.CollectionBase<MapLocation> empireHomeLocations)
        {
            var totalMinorRaces = 0;
            var minorRaceFrequency = GameContext.Current.Options.MinorRaceFrequency;
            var universe = GameContext.Current.Universe;
            var empires = new List<Civilization>();
            var minorRaces = new Dictionary<Quadrant, List<Civilization>>();
            var homeSystemDatabase = HomeSystemsDatabase.Load();
            var xmlSystems = new XmlDocument();
            var homeLocations = new Collections.CollectionBase<MapLocation>();

            var empireHomeLocationsCopy = empireHomeLocations = new Collections.CollectionBase<MapLocation>();

            List<Civilization> civs;

            xmlSystems.Load(ResourceManager.GetResourcePath(@"Resources\Data\HomeSystems.xml"));

            if (minorRaceFrequency != MinorRaceFrequency.None)
            {
                foreach (var quadrant in EnumHelper.GetValues<Quadrant>())
                    minorRaces[quadrant] = new List<Civilization>();
            }

            /*
             * Isolate empires so we can place them first, applying minimum
             * homeworld distance.
             */
            foreach (var civ in GameContext.Current.Civilizations)
            {
                if (civ.IsEmpire)
                {
                    empires.Add(civ);
                }
                else if (minorRaceFrequency != MinorRaceFrequency.None)
                {
                    if (!minorRaces.ContainsKey(civ.HomeQuadrant))
                        minorRaces[civ.HomeQuadrant] = new List<Civilization>();

                    minorRaces[civ.HomeQuadrant].Add(civ);
                    totalMinorRaces++;
                }
            }

            if (minorRaceFrequency != MinorRaceFrequency.None)
            {
                double minorRaceDivisor;
                var minorRaceTable = GameContext.Current.Tables.UniverseTables["MinorRaceFrequency"];
                if ((minorRaceTable == null) ||
                    !double.TryParse(minorRaceTable[minorRaceFrequency.ToStringCached()][0], out minorRaceDivisor))
                {
                    minorRaceDivisor = 4;
                }

                var maxMinorRaces = (int)((1.0 / minorRaceDivisor) * (positions.Count - empires.Count));
                if (maxMinorRaces > MinorRaceLimit)
                    maxMinorRaces = MinorRaceLimit;

                foreach (Quadrant quadrant in Enum.GetValues(typeof(Quadrant)))
                {
                    Algorithms.RandomShuffleInPlace(minorRaces[quadrant]);
                }

                while (totalMinorRaces > maxMinorRaces)
                {
                    var quadrantWithMostRaces = EnumHelper.GetValues<Quadrant>().MaxElement(o => minorRaces[o].Count);
                    if (minorRaces[quadrantWithMostRaces].Count == 0)
                        break;
                    minorRaces[quadrantWithMostRaces].RemoveAt(0);
                    totalMinorRaces--;
                }

                civs = new List<Civilization>(empires.Count + totalMinorRaces);

                civs.AddRange(empires);
                civs.AddRange(EnumHelper.GetValues<Quadrant>().SelectMany(q => minorRaces[q]));
            }
            else
            {
                civs = empires;
            }

            var unusedCivs = GameContext.Current.Civilizations.Except(civs).Select(o => o.CivID).ToHashSet();

            GameContext.Current.Civilizations.RemoveRange(unusedCivs);
            GameContext.Current.CivilizationManagers.RemoveRange(unusedCivs);

            var minHomeDistance = GetMinDistanceBetweenHomeworlds();
            Algorithms.RandomShuffleInPlace(positions);

            for (var index = 0; index < civs.Count; index++)
            {
                int iPosition;

                if (civs[index].IsEmpire)
                {
                    var localIndex = index;
                    
                    iPosition = Algorithms.FindFirstIndexWhere(
                        positions,
                        delegate(MapLocation location)
                        {
                            if (universe.Map.GetQuadrant(location) != civs[localIndex].HomeQuadrant)
                                return false;

                            return empireHomeLocationsCopy.All(t => MapLocation.GetDistance(location, t) >= minHomeDistance);
                        });
                }
                else
                {
                    var localIndex = index;

                    iPosition = Algorithms.FindFirstIndexWhere(
                        positions,
                        location => (universe.Map.GetQuadrant(location) == civs[localIndex].HomeQuadrant));
                }

                if (iPosition >= 0)
                {
                    var location = positions[iPosition];

                    if (civs[index].IsEmpire)
                        empireHomeLocations.Add(location);

                    homeLocations.Add(location);

                    Log.DebugFormat(
                        "Civilization {0} placed at location {1}",
                        civs[index].ShortName,
                        location);

                    positions.RemoveAt(iPosition);
                }
                else
                {
                    Log.WarnFormat(
                        "Failed to find a suitable home sector for civilization {0}.  Galaxy generation will start over.",
                        civs[index].ShortName);
                    civs.RemoveAt(index--);
                    return false;
                }
            }

            foreach (var civ in civs)
            {
                var civManager = new CivilizationManager(GameContext.Current, civ);

                GameContext.Current.CivilizationManagers.Add(civManager);

                var homeSystemDescriptor = (homeSystemDatabase.ContainsKey(civ.Key))
                                               ? homeSystemDatabase[civ.Key]
                                               : GenerateHomeSystem(civ);

                var planets = new List<Planet>();
                var race = civ.Race;
                var homeSystem = new StarSystem();

                if (!homeSystemDescriptor.IsNameDefined)
                {
                    if (starNames.Count > 0)
                    {
                        homeSystemDescriptor.Name = starNames[0];
                        starNames.RemoveAt(0);
                    }
                    else
                    {
                        homeSystemDescriptor.Name = civ.ShortName + " Home System";
                    }
                }

                homeSystem.Name = homeSystemDescriptor.Name;
                homeSystem.Location = homeLocations[0];
                homeLocations.RemoveAt(0);

                if (homeSystemDescriptor.IsInhabitantsDefined)
                    race = GameContext.Current.Races[homeSystemDescriptor.Inhabitants];

                if (homeSystemDescriptor.StarType.HasValue)
                {
                    homeSystem.StarType = homeSystemDescriptor.StarType.Value;
                }
                else
                {
                    StarType starType;
                    while (!(starType = GetStarType()).SupportsPlanets())
                        continue;
                    homeSystem.StarType = starType;
                }

                if (homeSystemDescriptor.HasBonuses)
                    homeSystem.AddBonus(homeSystemDescriptor.Bonuses);

                if (homeSystemDescriptor.Planets.Count == 0)
                    GeneratePlanetsWithHomeworld(homeSystemDescriptor, civ);
                else
                    GenerateUnspecifiedPlanets(homeSystemDescriptor);

                foreach (var planetDescriptor in homeSystemDescriptor.Planets)
                {
                    if (planets.Count >= StarHelper.MaxNumberOfPlanets(homeSystem.StarType))
                        break;

                    if (!planetDescriptor.IsSinglePlanet)
                        continue;

                    var planet = new Planet();

                    if (planetDescriptor.IsNameDefined)
                        planet.Name = planetDescriptor.Name;

                    if (planetDescriptor.Size.HasValue)
                        planet.PlanetSize = planetDescriptor.Size.Value;

                    if (planetDescriptor.Type.HasValue)
                    {
                        if (!planetDescriptor.Size.HasValue)
                        {
                            switch (planetDescriptor.Type)
                            {
                                case PlanetType.Asteroids:
                                    planet.PlanetSize = PlanetSize.Asteroids;
                                    break;
                                case PlanetType.GasGiant:
                                    planet.PlanetSize = PlanetSize.GasGiant;
                                    break;
                            }
                        }

                        planet.PlanetType = planetDescriptor.Type.Value;
                    }

                    if (planetDescriptor.HasBonuses)
                        planet.AddBonus(planetDescriptor.Bonuses);

                    planet.Variation = Statistics.Random(Planet.MaxVariations);
                    //PlaceMoons(planet);
                    planets.Add(planet);
                }

                homeSystem.AddPlanets(planets);

                SetPlanetNames(homeSystem);

                homeSystem.Owner = civ;
                universe.Map[homeSystem.Location].System = homeSystem;

                CreateHomeColony(civ, homeSystem, race);

                if (civManager.HomeColony == null)
                    civManager.HomeColony = homeSystem.Colony;

                civManager.Colonies.Add(homeSystem.Colony);

                PlaceBonuses(homeSystem);

                GameContext.Current.Universe.Objects.Add(homeSystem);
                GameContext.Current.Universe.Objects.Add(homeSystem.Colony);
            }

            return true;
        }

        private static void PlaceBonuses(StarSystem system)
        {
            if (system == null)
                throw new ArgumentNullException("system");

            var placementCount = 0;

            /*
             * Dilithium System Bonus
             */
            if (system.IsInhabited && system.Colony.Owner.CanExpand)
            {
                system.AddBonus(SystemBonus.Dilithium);
            }
            else if (DieRoll.Chance(4))
            {
                var habitablePlanets = from p in system.Planets
                                       where (p.PlanetType != PlanetType.GasGiant)
                                             && (p.PlanetType != PlanetType.Asteroids)
                                             && (p.PlanetType != PlanetType.Crystalline)
                                             && (p.PlanetType != PlanetType.Demon)
                                       select p;
                if (habitablePlanets.Any())
                {
                    system.AddBonus(SystemBonus.Dilithium);
                }
            }

            /*
             * Raw Materials System Bonus
             */
            if (system.IsInhabited && system.Colony.Owner.CanExpand)
            {
                system.AddBonus(SystemBonus.RawMaterials);
            }
            else if (DieRoll.Chance(3))
            {
                var habitablePlanets = from p in system.Planets
                                       where (p.PlanetType != PlanetType.GasGiant)
                                             && (p.PlanetType != PlanetType.Asteroids)
                                             && (p.PlanetType != PlanetType.Crystalline)
                                             && (p.PlanetType != PlanetType.Demon)
                                       select p;
                if (habitablePlanets.Any())
                {
                    system.AddBonus(SystemBonus.RawMaterials);
                }
            }

            /*
             * Energy Planet Bonus
             */
            foreach (var planet in system.Planets)
            {
                if (((planet.PlanetType == PlanetType.Volcanic) && !planet.HasEnergyBonus && DieRoll.Chance(2))
                    || ((planet.PlanetType == PlanetType.Desert) && !planet.HasEnergyBonus && DieRoll.Chance(3)))
                {
                    planet.AddBonus(PlanetBonus.Energy);
                    if (++placementCount >= 2)
                    {
                        break;
                    }
                }
            }

            placementCount = 0;

            /*
             * Food Planet Bonus
             */
            foreach (var planet in system.Planets)
            {
                if (((planet.PlanetType == PlanetType.Terran)
                     || (planet.PlanetType == PlanetType.Oceanic)
                     || (planet.PlanetType == PlanetType.Jungle))
                    && !planet.HasFoodBonus && DieRoll.Chance(3))
                {
                    planet.AddBonus(PlanetBonus.Food);
                    if (++placementCount >= 2)
                    {
                        break;
                    }
                }
            }
        }

        private static void GeneratePlanetsWithHomeworld(StarSystemDescriptor system, Civilization civ)
        {
            var homePlanet = new PlanetDescriptor();
            PlanetSize planetSize;
            homePlanet.Type = civ.Race.HomePlanetType;
            while (!(planetSize = (PlanetSize)RandomProvider.NextEnum(typeof(PlanetSize))).IsHabitable())
                continue;
            if (!system.IsStarTypeDefined)
            {
                while (!(system.StarType = GetStarType()).Value.SupportsPlanets() ||
                       (system.StarType.Value == StarType.Nebula))
                    continue;
            }
            homePlanet.Size = planetSize;
            homePlanet.Name = system.Name + " Prime";
            GeneratePlanets(system, StarHelper.MaxNumberOfPlanets(system.StarType.Value) - 1);
            system.Planets.Insert(
                GetIdealSlot(system, homePlanet),
                homePlanet);
        }

        private static void GenerateUnspecifiedPlanets(StarSystemDescriptor system)
        {
            GeneratePlanets(system, 0);
        }

        private static int GetDefinedPlanetCount(StarSystemDescriptor system)
        {
            var result = 0;
            foreach (var planetDescriptor in system.Planets)
            {
                if (planetDescriptor.IsSinglePlanet)
                    result++;
            }
            return result;
        }

        private static void GeneratePlanets(StarSystemDescriptor system, int maxNewPlanets)
        {
            int initialCount;
            if (!system.IsStarTypeDefined)
            {
                while (!(system.StarType = GetStarType()).Value.SupportsPlanets() ||
                       (system.StarType.Value == StarType.Nebula))
                    continue;
            }
            for (var i = 0; i < system.Planets.Count; i++)
            {
                if (!system.Planets[i].IsSinglePlanet)
                {
                    var attemptNumber = 0;
                    var newPlanets = 0;
                    var planetDescriptor = system.Planets[i];
                    initialCount = GetDefinedPlanetCount(system);
                    system.Planets.RemoveAt(i--);
                    while (((newPlanets < planetDescriptor.MinNumberOfPlanets) ||
                            (attemptNumber < planetDescriptor.MaxNumberOfPlanets))
                           &&
                           ((initialCount + attemptNumber) < StarHelper.MaxNumberOfPlanets(system.StarType.Value)))
                    {
                        var planetSize = GetPlanetSize(system.StarType.Value, initialCount);
                        if (planetSize != PlanetSize.NoWorld)
                        {
                            var planet = new PlanetDescriptor
                                         {
                                             Size = planetSize,
                                             Type = GetPlanetType(
                                                 system.StarType.Value,
                                                 planetSize,
                                                 initialCount + attemptNumber)
                                         };
                            system.Planets.Insert(++i, planet);
                            newPlanets++;
                        }
                        attemptNumber++;
                    }
                }
            }
            for (var i = 0; i < system.Planets.Count; i++)
            {
                if (system.Planets[i].IsSinglePlanet)
                {
                    if (!system.Planets[i].IsSizeDefined)
                    {
                        while ((system.Planets[i].Size = GetPlanetSize(system.StarType.Value, i)) == PlanetSize.NoWorld)
                            continue;
                    }
                    if (!system.Planets[i].IsTypeDefined)
                    {
                        system.Planets[i].Type = GetPlanetType(system.StarType.Value, system.Planets[i].Size.Value, i);
                    }
                }
            }
            initialCount = GetDefinedPlanetCount(system);
            for (var i = 0;
                 (i < maxNewPlanets) &&
                 ((initialCount + i) < StarHelper.MaxNumberOfPlanets(system.StarType.Value));
                 i++)
            {
                var planetSize = GetPlanetSize(system.StarType.Value, initialCount + i);
                if (planetSize != PlanetSize.NoWorld)
                {
                    var planet = new PlanetDescriptor
                                 {
                                     Size = planetSize,
                                     Type = GetPlanetType(system.StarType.Value, planetSize, initialCount + i)
                                 };
                    system.Planets.Add(planet);
                }
            }
        }

        private static void CreateHomeColony(Civilization civ, StarSystem system, Race inhabitants)
        {
            var civManager = GameContext.Current.CivilizationManagers[civ];
            var colony = new Colony(system, inhabitants);
            var baseMorale = GameContext.Current.Tables.MoraleTables["BaseMoraleLevels"];

            colony.Population.BaseValue = (int)(0.45f * system.GetMaxPopulation(inhabitants));
            colony.Population.Reset();
            colony.Name = system.Name;

            system.Colony = colony;

            if (baseMorale[civ.Key] != null)
                colony.Morale.BaseValue = Number.ParseInt32(baseMorale[civ.Key][0]);
            else
                colony.Morale.BaseValue = Number.ParseInt32(baseMorale[0][0]);

            colony.Morale.Reset();

            civManager.MapData.SetExplored(colony.Location, true);
            civManager.MapData.SetScanned(colony.Location, true, 1);

            GameContext.Current.Universe.HomeColonyLookup[civ] = colony;
        }

        private static void GenerateSystems(
            IEnumerable<MapLocation> positions,
            IList<string> starNames,
            IIndexedCollection<MapLocation> homeLocations)
        {
            int maxPlanets;
            var nebulaNames = GetNebulaNames();

            switch (GameContext.Current.Options.PlanetDensity)
            {
                case PlanetDensity.Sparse:
                    maxPlanets = StarSystem.MaxPlanetsPerSystem - 4;
                    break;
                case PlanetDensity.Medium:
                    maxPlanets = StarSystem.MaxPlanetsPerSystem - 2;
                    break;
                default:
                    maxPlanets = StarSystem.MaxPlanetsPerSystem;
                    break;
            }

            Algorithms.RandomShuffleInPlace(nebulaNames);

            Parallel.ForEach(
                positions,
                position =>
                {
                    StarType starType;

                    var system = new StarSystem();
                    var planets = new List<Planet>();

                    do { starType = GetStarType(); }
                    while (!StarHelper.CanPlaceStar(starType, position, homeLocations));
                    
                    system.StarType = starType;
                    system.Location = position;

                    switch (system.StarType)
                    {
                        case StarType.BlackHole:
                            system.Name = "Black Hole";
                            break;
                        case StarType.NeutronStar:
                            system.Name = "Neutron Star";
                            break;
                        case StarType.Quasar:
                            system.Name = "Quasar";
                            break;
                        case StarType.RadioPulsar:
                            system.Name = "Radio Pulsar";
                            break;
                        case StarType.XRayPulsar:
                            system.Name = "X-Ray Pulsar";
                            break;
                        case StarType.Nebula:
                            if (nebulaNames.Count == 0)
                                break;
                            system.Name = nebulaNames[0];
                            nebulaNames.RemoveAt(0);
                            break;
                        default:
                            if (starNames.Count == 0)
                            {
                                system.Name = "System " + system.ObjectID;
                                break;
                            }
                            system.Name = starNames[0];
                            starNames.RemoveAt(0);
                            break;
                    }

                    if (starType.SupportsPlanets())
                    {
                        for (var i = 0; i < maxPlanets - 1; i++)
                        {
                            var planetSize = GetPlanetSize(system.StarType, i);
                            if (planetSize != PlanetSize.NoWorld)
                            {
                                var planet = new Planet
                                             {
                                                 PlanetSize = planetSize,
                                                 PlanetType = GetPlanetType(system.StarType, planetSize, i),
                                                 Variation = Statistics.Random(Planet.MaxVariations)
                                             };
                                //planet.Slot = i;
                                //if (planet.PlanetType != PlanetType.Asteroids)
                                //    PlaceMoons(planet);
                                planets.Add(planet);
                            }
                            if (system.StarType == StarType.Nebula)
                                break;
                        }

                        system.AddPlanets(planets);
                        SetPlanetNames(system);
                        PlaceBonuses(system);
                    }

                    GameContext.Current.Universe.Objects.Add(system);
                    GameContext.Current.Universe.Map[position].System = system;
                }
                );
        }

        private static StarType GetStarType()
        {
            var result = StarType.White;
            var maxRoll = 0;
            foreach (StarType type in Enum.GetValues(typeof(StarType)))
            {
                var currentRoll = DieRoll.Roll(100 + StarTypeDist[type]);
                if (currentRoll > maxRoll)
                {
                    result = type;
                    maxRoll = currentRoll;
                }
            }
            return result;
        }

        private static int GetPlanetSizeScore(StarType starType, PlanetSize planetSize, int slot)
        {
            return DieRoll.Roll(100)
                   + StarTypeModToPlanetSizeDist[starType, planetSize]
                   + SlotModToPlanetSizeDist[slot, planetSize];
        }

        private static PlanetSize GetPlanetSize(StarType starType, int slot)
        {
            var result = PlanetSize.NoWorld;
            var maxRoll = 0;
            foreach (PlanetSize size in Enum.GetValues(typeof(PlanetSize)))
            {
                var currentRoll = GetPlanetSizeScore(starType, size, slot);
                if (currentRoll > maxRoll)
                {
                    result = size;
                    maxRoll = currentRoll;
                }
            }
            return result;
        }

        private static PlanetType GetPlanetType(StarType starType, PlanetSize size, int slot)
        {
            if (size == PlanetSize.Asteroids)
                return PlanetType.Asteroids;
            var result = PlanetType.Barren;
            var maxRoll = 0;
            foreach (PlanetType type in Enum.GetValues(typeof(PlanetType)))
            {
                var currentRoll = GetPlanetTypeScore(starType, size, type, slot);
                if (currentRoll > maxRoll)
                {
                    result = type;
                    maxRoll = currentRoll;
                }
            }
            return result;
        }

        private static int GetPlanetTypeScore(StarType starType, PlanetSize planetSize, PlanetType planetType, int slot)
        {
            return DieRoll.Roll(100)
                   + StarTypeModToPlanetTypeDist[starType, planetType]
                   + PlanetSizeModToPlanetTypeDist[planetSize, planetType]
                   + SlotModToPlanetTypeDist[slot, planetType];
        }

        private static void PlaceMoons()
        {
            var moons = new List<MoonType>(Planet.MaxMoonsPerPlanet);
            foreach (var system in GameContext.Current.Universe.Find<StarSystem>())
            {
                foreach (var planet in system.Planets)
                {
                    var handicap = 0;
                    moons.Clear();
                    if (planet.PlanetType == PlanetType.Asteroids)
                        continue;
                    for (var i = 0; i < Planet.MaxMoonsPerPlanet; i++)
                    {
                        var maxRoll = handicap;
                        var moonSize = MoonSize.NoMoon;
                        foreach (MoonSize moon in Enum.GetValues(typeof(MoonSize)))
                        {
                            var currentRoll = DieRoll.Roll(100)
                                              + PlanetSizeModToMoonSizeDist[planet.PlanetSize, moon]
                                              + PlanetTypeModToMoonSizeDist[planet.PlanetType, moon]
                                              - handicap;
                            if (currentRoll > maxRoll)
                            {
                                moonSize = moon;
                                maxRoll = currentRoll;
                            }
                        }
                        if (moonSize != MoonSize.NoMoon)
                        {
                            moons.Add(moonSize.GetType((MoonShape)RandomProvider.NextEnum(typeof(MoonShape))));
                        }
                        handicap += (maxRoll / Planet.MaxMoonsPerPlanet);
                    }
                    planet.Moons = moons.ToArray();
                }
            }
        }
    }
}